#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @author: Florian Dorn <florian.dorn@gmail.com>
# @package: /agenda
# @license: GNU GPLv2
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#from pysqlite2 import dbapi2 as sqlite
import os
import os.path
from gettext import gettext as _

import gconf
import traceback
try:
	from configobj import ConfigObj
except:
	ConfigObj = Noneupdate

import time
import datetime
import dateutil
from dateutil.parser import *

from agenda.pluginsystem import *
from agenda import *


from Config import Config

from threading import Thread
from Queue import Queue
import shelve

from agenda.CalendarProviderBase import CalendarProviderBase

SETTINGSDIR = os.path.expanduser("~/.gnome2/agenda")


if not os.path.exists(SETTINGSDIR):
	try:
			os.makedirs(SETTINGSDIR, 0744)
	except Exception , msg:
			print 'Could not create user dir (%s):\n %s' % (SETTINGSDIR, msg)
			print traceback.print_exc()
			exit

#CALDB = SETTINGSDIR + "/calendar.sqlite"
pickle_path = SETTINGSDIR+ "/pickle"
SETTINGS = SETTINGSDIR + "/settings.ini"
ANOTHER_DAY_FMT = "%d. %b."

DATE_INFO_FMT = "%A, %x"

OFFSET_TIME_DELTA =  datetime.datetime.now().replace(microsecond=0) - datetime.datetime.utcnow().replace(microsecond=0)

class ProviderUpdater:
	def __init__(self,provider):
		#Thread.__init__(self)
		self.provider = provider
		self.update_success = False
		self.updated_dt = datetime.datetime.now()
	def run(self):
		#print "updating %s" % self.provider.Plugin_Name
		self.provider.deleteEvents()
		self.provider.clearErrors()
		self.updated_dt = datetime.datetime.now()
		try:
			self.provider.read_events(True)
			self.update_success = True
		except:
			self.update_success = False


def generic_notify_callback(parent, pspec):
	print parent
	print "Setting %s ot %s" %(pspec.name,parent.get_property(pspec.name))

class CalCalendar(gobject.GObject):
	
	__gtype_name__ = 'CalCalendar'
	__gproperties__ = {
	    'name': ( gobject.TYPE_STRING, 'name' ,"Calendar name",  "",  gobject.PARAM_READWRITE ),
		'color': ( gobject.TYPE_STRING, 'color' ,"Color", "#ff0000", gobject.PARAM_READWRITE ),
		'visible': ( gobject.TYPE_BOOLEAN, 'visible',"Visibility",  True, gobject.PARAM_READWRITE ),
	}
	__gsignals__ = {
			'changed'	     : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
						[  ]), 
			'updating-started' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,  
						[ ]),
			'updating-finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, 
						[ ]),
		}

	id = ""
	provider = None
	access_level = "read"
	"""
	
	"""
	updated_dt = None
	__is_writeable = False
	
	items = None
	def __init__(self,id,name,color,updated_ext = None,provider = None,visible = True,access_level="read"):
		if(not isinstance(provider,CalendarProviderBase)):
			raise Exception()
		gobject.GObject.__init__(self)
		self.id = id
		self.provider = provider
		self.updated_int = datetime.datetime.utcnow().replace(microsecond=0)
		self.updated_ext = updated_ext
		
		self.__is_writeable = ( access_level != "read" )
		if(not isinstance(name,unicode)):
			name = unicode(name,"utf-8")
		if(not isinstance(color,unicode)):
			color = unicode(color,"utf-8")

		self._attributes = {'name': name, 'color': color,  'visible':visible}
		#self.items = ItemStorage()
	def print_full(self):
		print "id:", self.id
		print "provider:",  self.provider
		print "updated_int", self.updated_int
		print "updated_ext", self.updated_ext
		
	def do_get_property(self, pspec):
		if pspec.name in ('name', 'color', 'visible'):
			return self._attributes[pspec.name]
		else:
			raise AttributeError, 'unknown property %s' % pspec.name
			
	def do_set_property(self, pspec, value):
		if pspec.name in ('name', 'color', 'visible'):
			self._attributes[pspec.name] = value
			Emit(self,"changed")
		else:
			raise AttributeError, 'unknown property %s' % pspec.name
	def set_visibe(self,is_visible):
		print self, "is_visible", is_visible
		self.set_property("visible",is_visible)

	def get_visible(self):
		return self._attributes['visible']	
	def is_visible(self):
		return self._attributes['visible'] and self.provider.is_active()
	def is_writeable(self):
		return self.__is_writeable
	def set_color(self,color):
		self._attributes['color'] = color
	def get_name(self):
		return self._attributes['name']
	def get_color(self):
		return self._attributes['color']

	def __repr__(self):
		return "%s: %s: %s" % (id(self),self.provider, self.get_name())

	def __lt__(self, other):
		if isinstance(other, CalCalendar):
			return self._attributes['name'] < other._attributes['name']
		return NotImplemented
	def __gt__(self, other):
		if isinstance(other, CalCalendar):
			return self._attributes['name'] > other._attributes['name']
		return NotImplemented
	def __eq__(self,other):
			return 	(
					(self.id == other.id) and
					(self.provider == other.provider)
					)
	def create_event(self,what,start_time,end_time,where,description,recurrence_data):
		self.provider.create_event(self,what,start_time,end_time,where,description,recurrence_data)

	def __getstatue__(self):
		pass
	def __setstate__(self):
		pass
	def get_id(self):
		return self.id
	def serialize(self):
		r = self._attributes
		r['id'] = self.id
		r['provider'] = self.provider.__module__
		r['updated_ext']  = self.updated_ext
		r['updated_int']  = self.updated_int
		r['is_visible'] = self.get_visible()
		if(self.__is_writeable):
			r['access_level'] = 'write'
		else:
			r['access_level'] = 'read'			
		r['events'] = []
		return r
	@staticmethod
	def deserialize(r):
		a = CalCalendar(r['id'],r['name'],r['color'],r['updated_ext'],r['provider'],r['visible'],r['access_level'])
		return a
		
class CalEvent(gobject.GObject):
	
	__gtype_name__ = 'CalEvent'
	__gsignals__ = {
			'changed'	     : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
						[  ])
					}
	__attrs = ("id","calendar","start_time","end_time","title","comment","place","status","duration")
	def serialize(self):
		r = {}
		for key in self._attr.keys():
			if(key != "calendar"):
				r[key] = self._attr[key]
		return r
	@staticmethod
	def deserialize(r):
		#print "deserialize:", r
		event = CalEvent(r['id'],r['calendar'], r['start_time'], r['end_time'], r['title'], r['comment'], r['place'], r['status'])
		#print event.set_calendar(r['calendar'])
		return event
	"""
	
	"""
	def __init__(self, id,calendar, start_time, end_time, title, comment, place, status = "CONFIRMED"):
		gobject.GObject.__init__(self)
		"""initialize the Event Object from the Calendar DB-row"""
		"""Events(
		0: ev_id
		1: calendar_id
		2: start_time
		3: end_time
		4: duration
		5: title
		6: comment)
		7: place
		Calendar(
		8: cal_id
		9: name
		10: color
		11: updated_ext
		12: updated_int
		13: provider)
		"""
		self._attr = {}
		self._attr["id"]               = id
		self._attr["start_time"]       = (start_time)		#_cleanup_datetime
		self._attr["end_time"]         = (end_time)
		
		if(place == None):
			place = ""
		if(comment == None):
			comment = ""
		"""
		title = unicode(title,"utf-8")
		comment = unicode(comment,"utf-8")
		place = unicode(place,"utf-8")
		""" 
		self._attr["title"]            	= title
		self._attr["comment"]          	= comment
		self._attr["place"]          	= place
		self._attr["calendar"] 		= calendar

		calendar.connect("notify", self.calendar_property_change)
		
		self._attr["cal_name"] = self.calendar.get_name()

		self._attr["duration"] = self.end_time - self.start_time
		self._attr["status"]	= status
		null_time = datetime.time()
		self._attr["all_day"] = ((self._attr["start_time"].time() == null_time) and (self._attr["end_time"].time() == null_time))
		self.updated_dt = datetime.datetime.now()
		#print self.calendar
	def get_color(self):
		return self.calendar.get_color()

	def is_all_day(self):
		return self.all_day
	def is_past(self):
		"""return true if the event is ahead in time"""
		now = datetime.datetime.now()
		return self.end_time < now

	def is_today(self):
		return self.start_time.date() == datetime.date.today()

	def getDay(self):
		"""get the day as str("YYYY-MM-DD")"""
		return self.start_time.date().isoformat()
	def getStartDate(self):
		return self.start_time.date()
	def is_now(self):
		now = datetime.datetime.now()
		return now > self.start_time and now < self.end_time
	def is_confirmed(self):
		return self.status == "CONFIRMED"
	
	def __str__(self):
		#return "%s: %s (%s) [%s]" % (self.start_time,self.title,self.duration,self.updated_dt)
		return "%s" % self._attr
	def debug_info(self):
		return self._attr.__str__()
	def is_visible(self):
		return self.calendar.is_visible()
	
	def __lt__(self, other):
		if isinstance(other, CalEvent):
			# if dates are equal, order by name
			if self.start_time == other.start_time:
				return self.calendar.get_name() < other.calendar.get_name()
			else:
				return self.start_time < other.start_time 
		return NotImplemented
	def __gt__(self, other):
		if isinstance(other, CalEvent):
			# if dates are equal, order by name
			if self.start_time == other.start_time:
				return self.calendar.get_name() > other.calendar.get_name()
			else:
				return self.start_time > other.start_time
		return NotImplemented
	def __eq__(self, other):
		if isinstance(other, CalEvent):
			return (
				(self.id == other.id) and
				(self.start_time == other.start_time) and
				(self.end_time == other.end_time)
				)
		return NotImplemented
	def __ne__(self, other):
		result = self.__eq__(CalEvent)
		if result is NotImplemented:
			return result
		return not result
	def get_dur_sec(self):
		return self.duration.days * 86400 + self.duration.seconds
	def get_progress(self):
		if(self.is_now()):
			progress = self.duration - (self.end_time - datetime.datetime.now())
			progress = (progress.days * 86400 + progress.seconds ) / float(self.get_dur_sec())
			return progress
		return 0
	def get_uid(self):
		return "%s:%s:%s" % (self.start_time,self.cal_name,self.id)
	
	def calendar_property_change(self,cal, pspec):
		"""
		callback for changes in the underlying calendar
		"""
		#print "Setting %s ot %s for %s " %(pspec.name,cal.get_property(pspec.name),cal)
		#items = self.get_items_of_calendar(cal)
		Emit(self,"changed")
	def get_next_end(self):
		if self.duration > datetime.timedelta(days=1):
			return self.start_time + datetime.timedelta(days=1)
		else:
			return self.end_time
	def expand(self):
		"""
		Expand a > 24h event, returning 
		"""
		one_day = datetime.timedelta(days=1)
		if self.duration > one_day:
			start = self.start_time
			while start < self.end_time:
				evt = CalEvent(self.id, self.calendar, start, self.end_time, self.title, self.comment, self.place, self.status)
				yield evt
				start = start + one_day
		else:
				yield self
	def do_changed(self,a=None,b=None):
		pass
	def __getattr__(self,name):
		if self._attr.has_key(name):
			#print name
			return self._attr[name]
		else:
			#print self._attr.keys()
			print name
			raise AttributeError
	def __getstatue__(self):
		print "setting state of %s" % self
		return self._attr
	def __setstate__(self,data):
		print data
		self._attr = data
		print "setting state of %s" % self
	def update(self,other):
		#print "updating event"
		self.updated_dt = other.updated_dt
		self.place = other.place
		self.title = other.title
		Emit(self,"changed")
		#print self
		#print other

	def set_calendar(self,cal):
		self._attr["calendar"] = cal
		self._attr["cal_name"] = self.calendar.get_name()
		
class ItemStorage(gobject.GObject):
	"""
	this class stores Items (Event, TODO, etc...)
	"""

	__gsignals__ = {
		
		'item-added'		: (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
					[gobject.TYPE_PYOBJECT]),
		'item-removed'		: (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
					[gobject.TYPE_PYOBJECT]),
		#this signal is emitted if an item changes
		'items-changed'	 	: (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
					[gobject.TYPE_PYOBJECT]),
		#is emitted if a calendar is added
		'calendar-added'	: (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
					[gobject.TYPE_PYOBJECT]),					
		'ready'				: (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
					[ ]),

	}
	"""
	The list that actually contains the data
	"""
	__items = []
	__calendars = {}
	__providers = []
	def __init__(self,providers):
		gobject.GObject.__init__(self)
		self.__items = []
		self.__calendars = {}
		self.connect('item-added',self.self_insert_cb)
		self.__providers = providers
		print providers
		self.init_storage()
		self.get_from_storage()

	def add_item(self,item,emit = True):
		"""
		add an item to the list
		"""
		if not isinstance(item, CalEvent):
			raise Exception
		try:
			idx = self.__items.index(item)
			#print "Item is already in the list with index %s" % idx
			#print "  " + self.__items[idx].__str__()
			item.calendar = self.get_calendar(item.calendar.id)
			self.__items[idx].update(item)
			#Emit(self,'items-changed',item)
		except Exception,e:
			self.__items.append(item)
			if emit:
				Emit(self,'item-added',item)

	def remove_old_items_from_cal(self, calendar, older_than = None):
		l = []
		# build a list of items to be removed
		for i,item in enumerate(self.__items):
			if(item.calendar == calendar and item.updated_dt and item.updated_dt < older_than):
				l.append(item)

		#print "removing %s entries older than %s from %s" % (len(l),older_than, calendar)
		for item in l:
			print item
			self.remove_item(item)
		self.sync_to_storage()

	def remove_item(self,item):
		"""
		remove an item if it is in the list
		"""
		if item in self.__items:
			self.__items.remove(item)
			Emit(self,'item-removed',item)
			
	def get_items(self):
		"""
		get all items
		"""
		return self.__items
	def get_items_of_calendar(self,calendar):
		l = []
		for i in self.__items:
			try:
				if i.calendar == calendar :
					l.append(i)
			except:
				print "Failed to find Calendar for: %s" % i
		return l
			
	def add_calendar(self,calendar,emit = True):
		"""
		ItemStorage.add_calendar: add a calendar to the local storage
		"""
		if not isinstance(calendar, CalCalendar):
			raise Exception
		
		#print "trying to add calendar", calendar.get_name(), calendar
		try:
			p_cal = self.__calendars[calendar.id]
			#print p_cal.get_name()," - ", calendar.get_name()
			p_cal.set_property("name",calendar.get_name())
			p_cal.set_property("color",calendar.get_color())
			calendar.connect('updating-finished',self.on_cal_updating_finished)
		except:
			#idx = self.__calendars.index(calendar)
			#print "adding cal", calendar, calendar.get_name()
			self.__calendars[calendar.id] = calendar
			calendar.connect('updating-finished',self.on_cal_updating_finished)
			#print "Connected %s.'updating-finished' to self.on_cal_updating_finished" % calendar
			if emit:
				Emit(self,'calendar-added',calendar)
				pass
		return self.__calendars[calendar.id]
	def on_cal_updating_finished(self,cal):
		#cal.print_full()
		#print "%s finished updating" % cal
		#print "TODO: remove items not updated since %s" % cal.updated_int
		self.remove_old_items_from_cal(cal, utc2local( cal.updated_int ) )
	def get_calendar(self,id):
		"""
		get the calendar having with id
		"""
		
		if self.__calendars.has_key(id) :
			return self.__calendars[id]
		return None
	def get_calendars(self):
		"""
		"""
		c = self.__calendars.values()
		c.sort()
		return c
	def dbg_calendars(self):
		print "Appointments.dbg_calendars"
		for i,c in enumerate(self.get_calendars()):
			print i,":", c.get_name(),c
	def set_calendar_visible(self,id,active):
		"""
		Set the visibility of calendar id
		"""
		self.__calendars[id].set_visibe(active)
		
	def __del__(self):
		"""
		
		"""
		self.sync_to_storage()
	def sync_to_storage(self):
		"""
		write the current items to the local storage (=DB)
		"""
		#print "Writing to storage ('%s')" % pickle_path
		try:
			os.remove(pickle_path)
		except:
			pass
		try:
			db = shelve.open(pickle_path,'n')
			for cal_key,cal in self.__calendars.items():
			
				cal_dict = cal.serialize()
			
				for event in self.get_items_of_calendar(cal):
					cal_dict["events"].append(event.serialize())
					#print event._attr
			
				db[cal.get_id()] = cal_dict
			
			db.close()
		except:
			print "error writing to storage backend"
			print traceback.print_exc()
	def get_from_storage(self):
		try:
			now = datetime.datetime.now()
			db = shelve.open(pickle_path)
			if (db):
				for key,cal in db.items():
					#print key
					cal["provider"] = self.get_provider(cal["provider"])
					
					#print cal
					calendar = CalCalendar.deserialize(cal)
					calendar = self.add_calendar(calendar,False)
					for r in cal['events']:
						r['calendar'] = self.get_calendar(calendar.id)
						event = CalEvent(r['id'],r['calendar'], r['start_time'], r['end_time'], r['title'], r['comment'], r['place'], r['status'])
						event.updated_dt = now
#						print "deserialized: "
#						print event
						self.add_item(event,False)
			else:
				print "could not open db"
		except:
			print "Could not open storage"
			print traceback.print_exc()
		"""
		read data from storage and add calendars and items
		"""
		db.close()
		self.__items.sort()
		return

	def get_provider(self,provider):
		#provider = [prov for prov in self.__providers if prov.__module__ == row[5]][0]
		ret = None
		#print self.__providers
		#print 	(type(provider) == 'str')
		if(isinstance(provider,str)):
			ret = [prov for prov in self.__providers if prov.__module__ == provider][0]
		else:
			ret = [prov for prov in self.__providers if prov == provider][0]
		#print ret, type(ret)
		return ret
	def init_storage(self):
		Emit(self,'ready')

	def self_insert_cb(self,self2,item):
		"""
		this is just a dummy to test insertion
		"""
		#print "[%s] was inserted to list" % (item)
		
	"""
	Emulating container types
	http://docs.python.org/reference/datamodel.html#emulating-container-types
	"""
	def __iter__(self):
		return self.__items.__iter__()
	def __len__(self):
		return len(self.__items)
	def __getitem__(self,key):
		return self.__items[key]
	def __contains__(self,item):
		return self.__items.__contains__(item)


class AppEvt:
	"""
	This class is used for communication of the threaded part and the non-threaded part
	"""
	UPDATE = "update"
	GET_EVENTS = "get-events"
	STOP		= "stop"
	GET_CALENDARS = "get-calendars"
	SET_CALENDAR_VIS = "set-calendar-visibility"
	INSTERT_LOCAL	 = "insert-event-local"
	def __init__(self,command,param = None):
		self.cmd = command
		self.param = param



class MyQueue(Queue):
	def put(self,i):
		print "QUEUE: adding %s" % i.cmd
		Queue.put(self,i)



class Appointments (gobject.GObject, Thread):
	__shared_state = {}
	__gsignals__ = {
		'active-provider-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_STRING, gobject.TYPE_BOOLEAN]),
		'calendar-visibility-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_STRING, gobject.TYPE_BOOLEAN]),
		'updating-provider-started' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
		'updating-provider-finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
	    'updating-calendars-finished': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
	    'updating-calendars-start': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
	    'updated-event-store' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
		'close'			:  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[]),
		'config-change' :  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_STRING,gobject.TYPE_STRING]),
		'first-time-usage' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[]),
		'event-added':(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,[gobject.TYPE_PYOBJECT]),
	}

	item_storage = None
	
	"""
	A list of all calendar providers that are available
	"""
	providers = []
	"""
	the time of the last update
	"""	
	updated_dt = None
	instance = None

	def __init__(self,**kwargs):
		super(Appointments,self).__init__(**kwargs)
		Thread.__init__(self)
		self.daemon = True
		self.event_queue = MyQueue()
		self.first_time_usage = False
		self.result_queue = Queue()
		
		
		self.config = Config()
		
		
		if not '_ready' in dir(self):
			migrated = False
			if os.path.exists(SETTINGS) and ConfigObj is not None:
				migrated = True
				self.settings = ConfigObj(SETTINGS,write_empty_values=True,create_empty=True)
				#set default values
				self.settings.setdefault('plugins', {})
				self.settings.setdefault('width', '350')
				self.settings.setdefault('height','500')
				self.settings.setdefault('hide_finished', 'True')
				self.settings.setdefault('refresh', '30')
				self.settings.setdefault('reminder', '15')
				self.settings.setdefault('default_color','AABBCC')
				self.settings.setdefault('active_providers',[])
				self.settings.setdefault('num_days','30')
				self.settings.setdefault('accelerator',"<Alt>F12")
			#self.providers = get_by_capability('provider')
			self.providers = load_by_capability('provider',self)
			#register all plugins for ocnfig
			for p in self.providers:
				self.config.register_plugin(p)
			
			if migrated:
				#print "migrating settings"
				self.config.migrate_settings(self.settings)
				os.rename(SETTINGS,SETTINGS+".bak")
				pass
				
			self.first_time_usage = self.config.first_time_usage()
			
			self.initializeProviders()
			
		self.item_storage = ItemStorage(self.providers)
		self.connect("updating-provider-started",self.on_provider_update_started)
		self.connect("updating-provider-finished",self.on_provider_update_finished)
		self._ready = True
		


	def create_event(self,calendar, what="new Event (2)",start_time=None,end_time=None,where="",description="",recurrence_data=None):
		#TODO: move this to the thread 
		"""
		create an event.
		start_time and end_time are datetime objects without TZinfo, which is interpreted as GMT
		recurrence data follows the iCalendar standard (RFC 2445 http://www.ietf.org/rfc/rfc2445.txt)
		"""
		if start_time == None:
			start_time = _cleanup_datetime(datetime.datetime.now())
			if start_time.time().minute < 29:
				start_time = start_time.replace(minute=30)
			else:
				start_time = start_time.replace(hour=start.hour + 1,minute=0)
	
			end_time = start_time + datetime.timedelta(seconds=3600)
		
		calendar.create_event(what,start_time,end_time,where,description,recurrence_data)

	def get_active_providers(self):
		"""
		return a list of active providers
		"""
		l = []
		for p in self.providers:
			if(p.active):
				l.append(p)
		return l
	def today(self):
		"""
		get the current day
		"""
		return datetime.datetime.utcnow().date()
	def setCalendarHidden(self,id,active):
		"""
		set the visibility of calendar id
		"""
		self.item_storage.set_calendar_visible(id,active)
	def run(self):
		"""
		This is the main function in this thread.
		"""	
		self.running= True
		#print "Started Thread"		
		if(self.first_time_usage):
			print "emitting first_time_usage"
			Emit(self,'first-time-usage')

		while self.running:
			#wait for the update event
			#print "waiting for an event..."
			event = self.event_queue.get()
			if(event):
				#print "received: %s" % event
				pass
			try:
				if(event.cmd == AppEvt.UPDATE):
					self.__updateCalendars()
				elif event.cmd == AppEvt.GET_EVENTS:
					self.__getEvents()
				elif event.cmd == AppEvt.STOP:
					self.running = False
				elif event.cmd == AppEvt.GET_CALENDARS:
					self.__getCalendars(event.param)
				elif event.cmd == AppEvt.SET_CALENDAR_VIS:
					self.__setCalendarHidden(event.param[0],event.param[1])
			except 	Exception,e:
				print traceback.print_exc()
				
		Emit(self,'close')
		
	def stop(self):
		self.event_queue.put(AppEvt(AppEvt.STOP))
	def __del__(self):
		self.config.sync()
		pass

	def calendar_updated_cb(self, item_storage,calendar):
		print "Appointment.calendar_updated_cb"
		print item_storage, calendar
		pass
	def provider_calendar_added_cb(self,provider,cal):
		self.add_calendar(cal)
	def provider_event_added_cb(self,provider,event):
		"""
		this is called if an event was added inside a provider. 
		
		This can be if a new event is created, or if agenda is refreshed 
		"""
		c = self.item_storage.get_calendar(event.calendar.id)
		#print "adding event to:", event.calendar.id, c
		event.set_calendar(c)
		self.insert_event(event)
		
	def insert_event(self,event):
		#event.updated_dt = self.updated_dt
		self.item_storage.add_item(event)
	def toggle_provider_active(self,provider,active):
		#print "setting %s to %s" % (provider,active)
		if(active != provider.is_active):
			provider.set_active(active)
			self.config.set_list('active_providers',self.get_active_providers())
			Emit(self,'active-provider-changed',provider,active)
			#TODO:? emit the calendar vis change signal for all depending calendars...?
	def initializeProviders(self):
		#self.providers = get_by_capability('provider')
		
		for provider in self.providers:
			module = provider.__module__
			active = provider.__module__ in self.config.get_list('active_providers')
			provider.set_active(active)
			self.connect('config-change',provider.config_change_cb)
			provider.connect('event-added',self.provider_event_added_cb)
			provider.connect('calendar-added',self.provider_calendar_added_cb)
			
			provider_settings = self.config.get_plugin_settings(provider)
			provider.initPlugin(provider_settings,self)

	def on_provider_update_started(self, appointment, provider_updater):
		#print "provider '%s' is updating" % provider_updater
		print 
	def on_provider_update_finished(self, appointment, provider_updater):
		#print "provider '%s' has finished updating. TODO: cleanup the itemstore" % provider_updater
		pass

	def getErrors(self):
		errors = []
		for provider in self.get_active_providers():
			if(len(provider.getErrors())):
				prov_errors = [provider.Plugin_Name,provider.getErrors()]
				errors.append(prov_errors)
		if(len(errors)):		
			print errors
		return errors
	def clearErrors(self):
		"""Clear all errors of the providers"""
		for provider in self.get_active_providers():
			provider.clearErrors()		
	def updateCalendars(self):
		self.event_queue.put(AppEvt(AppEvt.UPDATE))
	def __updateCalendars(self):
		self.update_dt = datetime.datetime.now()
		Emit(self,'updating-calendars-start',self.updated_dt)
		###
		### use dbus to check for an active network connection
		###
		"""
		http://people.redhat.com/dcbw/NetworkManager/NetworkManager%20DBUS%20API.txt
		DBUS Service:			"org.freedesktop.NetworkManager"
		DBUS Object Path:		"/org/freedesktop/NetworkManager"
		DBUS Interface:		"org.freedesktop.NetworkManager"


		Name:	status			Retrieve status information about the network state
		Args:	(none)
		Returns:	DBUS_TYPE_STRING			"connecting"	- there is a pending network connection (waiting for a
														DHCP request to complete, waiting for an encryption
														key/passphrase, waiting for a wireless network, etc)
										"connected"	- there is an active network connection
										"scanning"	- there is no active network connection, but NetworkManager
														is looking for an access point to associate with
										"disconnected"	- there is no network connection
		"""
		
		print "updating calendars started at: %s" % self.update_dt
		
		num_cals = len(self.get_active_providers())
		
		providers = self.get_active_providers()
		#print providers
		for i,provider in enumerate( providers ) :
			try:
				pu = ProviderUpdater(provider)
				Emit(self,'updating-provider-started',	pu)
				pu.run()
				Emit(self,'updating-provider-finished',	pu)
			except Exception, e:
				print traceback.print_exc()
				print e

		print self.item_storage.dbg_calendars()
		Emit(self,'updating-calendars-finished',datetime.datetime.now)

	def add_calendar(self,calendar):
		calendar.updated_dt = self.update_dt
		#print "We are asked to add a calendar:"
		#print calendar
		self.item_storage.add_calendar( calendar )

	def insertEventLocal(self,id,calendar_id, start_time, end_time, title, comment="",place = "", status = ""):
		#param = [id,calendar_id, start_time, end_time, title, comment,place, status]
		#print "insertEventLocal"
		print "insertEventLocal"
		
		row = []
		row.append(id)
		row.append(calendar_id)
		row.append(start_time)
		row.append(end_time)
		row.append("")
		row.append(title)
		row.append(comment)
		row.append(place)
		row.append(None)
		row.append("")
		row.append("#ffffff")
		print calendar_id
		cal_event = CalEvent(id,calendar_id, start_time, end_time, title, comment,place, status)
		print cal_event
		#self.item_storage.add_item(cal_event)
		
	def __insertEventLocal(self,param):
		print " __insertEventLocal %s" % param
		id			= param[0]
		calendar_id	= param[1]
		start_time	= param[2]
		end_time	= param[3]
		title		= param[4]
		comment		= param[5]
		place		= param[6]
		status 		= param[7]
		
		"""insert events into the database"""
		"""times are eigther:
		Date only       (10):	2008-04-16
		Date and time   (23): 	2008-03-31T20:00:00.000
		Date, time tz  (>23):	2008-03-31T11:00:00.000+02:00

		0: ev_id
		1: calendar_id
		2: start_time
		3: end_time
		4: duration
		5: title
		6: comment)
		Calendar(
		7: cal_id
		8: name
		9: color
		10: status
		11: notify
		"""
		comment = comment.replace("\r","").replace("\n"," ")
		
		sql = "INSERT INTO Events values(?, ?, ?, ?, ?, ?, ?,?)"
		
		row = []
		row.append(id)
		row.append(calendar_id)
		row.append(start_time)
		row.append(end_time)
		row.append("")
		row.append(title)
		row.append(comment)
		row.append(place)
		row.append(None)
		row.append("")
		row.append("#ffffff")


		cal_event = CalEvent(row)
		print cal_event

		if(cal_event.duration.days > 1):
			#print str(cal_event.duration) + " days:" +title
			while (cal_event.end_time > cal_event.start_time):
				
				if (cal_event.end_time > cal_event.start_time):
					#print " %s - %s" % (cal_event.start_time,cal_event.end_time)
					try:
						self.item_storage.add_item(cal_event)
					except Exception,e:
						print traceback.print_exc()
						print e
						print row
				cal_event.start_time = (cal_event.start_time + datetime.timedelta(days=1))
		else:		
			#print (id,calendar_id, cal_event.start_time, cal_event.end_time,str(cal_event.duration), cal_event.title,cal_event.comment,cal_event.place)
			try:
				self.cur.execute(sql ,(id,calendar_id, cal_event.start_time, cal_event.end_time,str(cal_event.duration), cal_event.title,cal_event.comment,cal_event.place))
			except Exception,e:
				print traceback.print_exc()
				print e
				print row
	def get_calendar_data(self,id):
		print "appointment::get_calendar_data is obsolete, use get_calendar instead"
		return self.get_calendar(id)
		
	def get_calendar(self,id):
		"""
		get calendar by id
		"""
		return self.item_storage.get_calendar(id)
	def calendar_exists(self,id):
		row = self.get_calendar(id)
		return row == None

	def getCalendars(self,provider = None):
		l = []
		cals = self.item_storage.get_calendars()
		for p in cals:
			if p.provider == provider:
				l.append(p)
		return l
	def query(self,sql,param = None):
		"""
		This is used internally
		"""
		print "appointment.query"

	def set_option(self,path,value):
		"""
		set an option
		"""
		self.config.set(path,value)
		Emit(self,'config-change',path,value)
	def get_option(self,path):
		"""
		return an option
		"""
		value = self.config.get(path)
		
		return value
	def get_period(self):
		p = TimePeriod(datetime.timedelta(days=self.get_option('num_days')))
		return p



def _cleanup_datetime(datetime):
	return datetime.replace(second = 0, microsecond = 0)
	
def adapt_datetime(ts):
	"""return the string representation of the given timestamp"""
	ts.replace(microsecond = 0)
	if(ts.utcoffset()):
		ts = (ts.replace(tzinfo=None) - ts.utcoffset())
	if(ts.time() == datetime.time()):
		return ts.date().isoformat()
	else:
		return ts.isoformat(" ")

def convert_datetime(s):
	"""returns database date/times stored in UTC as local times"""
	if(len(s) == 10):
		return dateutil.parser.parse(s)
	else:
		dt = dateutil.parser.parse(s)
		return utc2local(dt)

def utc2local(dt):
	return datetime.datetime.fromtimestamp(time.mktime(dt.utctimetuple()) - time.timezone)
def local2utc(dt):
	"""Return the UTC value for the given date
	if datetime has no timezone set, it is treated a local time and converted to a utc time"""
	if(dt.utcoffset() or dt.tzinfo):
		dt = (dt.replace(tzinfo=None) - dt.utcoffset())	
		if(dt.tzinfo):
			dt = dt.replace(tzinfo=None)
		return dt
	else:
		return dt - OFFSET_TIME_DELTA
	
